package com.sdgs.paas.sdk.core.web.loadbalance;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import javax.script.ScriptException;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.netflix.zuul.context.RequestContext;
import com.sdgs.paas.sdk.core.log.PaasLogger;
import com.sdgs.paas.sdk.core.utils.ExpressionUtils;
import com.sdgs.paas.sdk.core.utils.JSONUtils;
import com.sdgs.paas.sdk.core.web.PaasHttpHeader;

/**
 * 按配置加权轮询服务标识，对应服务标识的实例权重随机负载均衡策略
 *
 * @author wjj
 *
 */
public class LabelAndWeightMetadataRule extends ZoneAvoidanceRule {

	@Autowired
	PaasHttpHeader paasHttpHeader;

	private final String PLACEHOLDER_PREFIX = "{{";
	private final String PLACEHOLDER_SUFFIX = "}}";
	private final String META_LBTABLE = "lbtable";
	public static final String META_DATA_KEY_LABEL_OR = "labelOr";
	public static final String META_DATA_KEY_WEIGHT = "weight";
	private Map<String, String> currentcfg = new ConcurrentHashMap<>();
	// 保存每个服务每种条件的总体权重，key是 服务_条件
	private Map<String, Integer> labelTotal = new ConcurrentHashMap<>();
	// 保存每个服务每种条件下每种label的权重状态，key是 服务_条件
	private Map<String, List<LabelWeight>> labelStatus = new ConcurrentHashMap<>();

	private Random random = new Random();

	/**
	 * 从http头上取出表达式中的变量值
	 * 
	 * @param condition
	 * @param request
	 * @return
	 */
	private Map<String, String> parseCondition(String condition) {

		HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
		Map<String, String> vars = new HashMap<>();

		StringBuffer buf = new StringBuffer(condition);
		int startIndex = buf.indexOf(PLACEHOLDER_PREFIX);
		while (startIndex != -1) {
			int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
			if (endIndex != -1) {
				String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
				int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();

				String val = null;
				// 如果是网关
				if (request != null) {
					val = request.getHeader(placeholder);
				} else {
					val = paasHttpHeader.getFromOrigHeader(placeholder);
				}

				val = val == null ? "" : val;
				vars.put(placeholder, val);

				startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);
			} else {
				startIndex = -1;
			}
		}
		return vars;

	}

	@Override
	public Server choose(Object key) {
		List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);
		// 一个也找不到，返回null
		if (CollectionUtils.isEmpty(serverList)) {
			return null;
		}
		int index = this.random.nextInt(serverList.size());
		boolean cfgchanged = false;
		DiscoveryEnabledServer defaultserver = ((DiscoveryEnabledServer) serverList.get(index));

		// 目的服务名
		String app = defaultserver.getInstanceInfo().getAppName().toUpperCase();
		Map<String, String> metadata = defaultserver.getInstanceInfo().getMetadata();
		// 没有配置metadata，随机选一个
		if (metadata == null) {
			return defaultserver;
		}
		String lbtableStr = metadata.get(META_LBTABLE);
		// 没有配置lbtable，随机选一个
		if (lbtableStr == null) {
			return defaultserver;
		} else {
			long start = System.currentTimeMillis();
			// 从容器取原来的配置
			String currentlb = currentcfg.get(app);
			if (currentlb != null) {
				// 有配置，比较是否变动，打标记
				cfgchanged = !currentlb.equals(lbtableStr);
			}
			// 存入配置
			currentcfg.put(app, lbtableStr);
			PaasLogger.metricInfo("路由配置变动检测耗时：{}，cfgchanged：{}", System.currentTimeMillis() - start, cfgchanged);
		}
		long start = System.currentTimeMillis();
		// String jsonstr = new
		// StringBuilder().append("{\"").append(META_LBTABLE).append("\":").append(lbtableStr)
		// .append("}").toString();
		PaasLoadBalanceConfig config;
		try {
			config = JSONUtils.json2pojo(lbtableStr, PaasLoadBalanceConfig.class);
		} catch (Exception e) {
			PaasLogger.error(e, "路由json转换异常");
			// 异常，随机选一个
			return defaultserver;
		}
		PaasLogger.metricInfo("路由配置：{}，json转对象执行耗时：{}", lbtableStr, System.currentTimeMillis() - start);

		// 路由配置
		List<ConditionRoute> cRoutes = config.getLbtable();
		// 条件路由按order从小到大排序
		cRoutes.sort((ConditionRoute c1, ConditionRoute c2) -> {
			return c1.getOrder() - c2.getOrder();
		});
		// 循环条件路由
		for (ConditionRoute cr : cRoutes) {
			String condition = cr.getCondition();
			String exp = condition.replaceAll("\\{", "").replaceAll("\\}", "");
			// 从http头上取出表达式中的变量值
			Map<String, String> map = parseCondition(condition);
			try {
				// 表达式计算
				if (ExpressionUtils.eval(exp, map)) {
					PaasLogger.info("路由命中：{}", exp);
					Map<String, String> route = cr.getRoute();
					Set<String> labels = route.keySet();

					int totalWeight = 0;
					List<LabelWeight> labelWeights = null;
					String labelTotalKey = app + "_" + condition;

					// 如果没有运行数据或者路由配置变化，那么要初始化运行数据
					if (labelTotal.get(labelTotalKey) == null || cfgchanged) {
						labelWeights = new CopyOnWriteArrayList<>();
						for (String label : labels) {
							String strWeight = route.get(label);

							int weight = 0;
							try {
								weight = Integer.parseInt(strWeight);
							} catch (Exception e) {
								// 无需处理
							}

							if (weight <= 0) {
								continue;
							}
							LabelWeight lw = new LabelWeight();
							lw.setLabel(label);
							lw.setWeight(weight);
							lw.setCurweight(weight);
							labelWeights.add(lw);
							totalWeight += weight;

						}

						labelTotal.put(labelTotalKey, totalWeight);
						labelStatus.put(labelTotalKey, labelWeights);
					} else {
						PaasLogger.info("配置未变更，继续计数");

						totalWeight = labelTotal.get(labelTotalKey);
						labelWeights = labelStatus.get(labelTotalKey);
						// 每次计算前对每个label的当前权重增加设定权重
						labelWeights.parallelStream().forEach(e -> {
							e.setCurweight(e.getCurweight() + e.getWeight());
						});
					}
					// 排序
					labelWeights.sort((l1, l2) -> {
						return l1.getCurweight() - l2.getCurweight();
					});
					// 取最大权重
					LabelWeight choosed = labelWeights.get(labelWeights.size() - 1);
					choosed.setCurweight(choosed.getCurweight() - totalWeight);

					String servLabel = choosed.getLabel();
					List<Server> matchList = serverList.parallelStream().filter(server -> {
						Map<String, String> currentmetadata = ((DiscoveryEnabledServer) server).getInstanceInfo()
								.getMetadata();
						// 从meta里获取label
						String metalabel = currentmetadata.get(META_DATA_KEY_LABEL_OR);
						// 返回标签不为空，并且包含servLabel的server
						return (!StringUtils.isEmpty(metalabel)) && metalabel.contains(servLabel);

					}).collect(Collectors.toList());
					PaasLogger.info("标签：{},实例数：{}", servLabel, matchList.size());

					// 如果标签匹配不上，随机返回一个server
					if (matchList.size() == 0) {
						return defaultserver;
					}

					// 标签匹配后，对同一个标签的实例，加权随机
					int instanceTotalWeight = 0;
					Map<Server, Integer> serverWeightMap = new HashMap<>();

					for (Server server : matchList) {

						Map<String, String> currentmetadata = ((DiscoveryEnabledServer) server).getInstanceInfo()
								.getMetadata();
						String strWeight = currentmetadata.get(META_DATA_KEY_WEIGHT);
						// 没配权重，就设置为1
						strWeight = strWeight == null ? "1" : strWeight;
						int weight = 0;
						try {
							weight = Integer.parseInt(strWeight);
						} catch (Exception e) {
							// 无需处理
						}

						if (weight <= 0) {
							continue;
						}

						serverWeightMap.put(server, weight);
						instanceTotalWeight += weight;

					}

					// 权重随机
					int randomWight = this.random.nextInt(instanceTotalWeight);
					int current = 0;
					for (Map.Entry<Server, Integer> entry : serverWeightMap.entrySet()) {
						// 不断叠加，最终等于instanceTotalWeight，则必会选中一个
						current += entry.getValue();
						if (randomWight <= current) {
							return entry.getKey();
						}
					}

				}
			} catch (ScriptException e) {
				PaasLogger.error(e, "路由表达式解析异常");
				// 异常，随机选一个
				return defaultserver;
			}

		}

		return defaultserver;
	}

	public Map<String, Integer> getLabelTotal() {
		return labelTotal;
	}

	public void setLabelTotal(Map<String, Integer> labelTotal) {
		this.labelTotal = labelTotal;
	}

	public Map<String, List<LabelWeight>> getLabelStatus() {
		return labelStatus;
	}

	public void setLabelStatus(Map<String, List<LabelWeight>> labelStatus) {
		this.labelStatus = labelStatus;
	}

	public Random getRandom() {
		return random;
	}

	public void setRandom(Random random) {
		this.random = random;
	}

	public String getPLACEHOLDER_PREFIX() {
		return PLACEHOLDER_PREFIX;
	}

	public String getPLACEHOLDER_SUFFIX() {
		return PLACEHOLDER_SUFFIX;
	}

	public Map<String, String> getCurrentcfg() {
		return currentcfg;
	}

	public void setCurrentcfg(Map<String, String> currentcfg) {
		this.currentcfg = currentcfg;
	}

	public String getMETA_LBTABLE() {
		return META_LBTABLE;
	}
}
